package clang.control;

import java.util.ArrayList;
import java.util.HashMap;

import clang.data.VarType;
import clang.data.Variable;
import clang.functions.Function;
import clang.functions.FunctionCall;
import clang.functions.StdLib;

public class ExecutionThread {
	private ArrayList<CodeBlock> blockStack = new ArrayList<CodeBlock>();
	private ArrayList<Function> functionStack = new ArrayList<Function>();
	
	private static ExecutionThread instance = null;
	private CodeBlock global = null;
	
	private HashMap<String, Function> functions = new HashMap<String, Function>();
	
	private ExecutionThread()
	{
		global = new CodeBlock(null);
		blockStack.add(global);
		instance = this;
	}
	
	public static ExecutionThread get()
	{
		if(instance == null)
			new ExecutionThread();
		return instance;
	}
	
	public void pushBlock(CodeBlock cb)
	{
		blockStack.add(cb);
	}
	
	public void popBlock(CodeBlock cb)
	{
		if(blockStack.get(blockStack.size() - 1) != cb || blockStack.size() == 0)
		{
			System.out.println("Block stack mismatch!");
		}
		
		blockStack.remove(blockStack.size() - 1);
	}
	
	public void pushFunction(Function f)
	{
		functionStack.add(f);
	}
	
	public void popFunction(Function f)
	{
		if(functionStack.get(functionStack.size() - 1) != f || functionStack.size() == 0)
		{
			System.out.println("Function stack mismatch!");
		}
		
		functionStack.remove(functionStack.size() - 1);
	}
	
	public CodeBlock getCurrentBlock()
	{
		if(blockStack.size() == 0)
			return null;
		else
			return blockStack.get(blockStack.size() - 1);
	}
	
	public Function getCurrentFunction()
	{
		if(functionStack.size() == 0)
			return null;
		else
			return functionStack.get(functionStack.size() - 1);
	}
	
	public CodeBlock getRootBlock()
	{
		return global;
	}
	
	public void addFunction(Function func)
	{
		functions.put(func.getSignature().getName(), func);
	}
	
	public Function findFunction(FunctionCall sig)
	{
		Function f = functions.get(sig.getName());
		getCurrentBlock().addCachedCall(sig);
		
		if(f != null)
			if(f.getSignature().matches(sig))
				return f;
		
		return StdLib.get().findFunction(sig);
	}
	
	public Variable executeCall(FunctionCall call) throws Exception
	{
		//System.out.println("executing call : " + call);
		pushFunction(call.getFunction());
		Variable ret = call.calculate();
		popFunction(call.getFunction());
		return ret;
	}
	
	public void runProgram() 
	{
		FunctionCall mainCall = new FunctionCall("main");
		try {
			Variable retVal = executeCall(mainCall);
			if(retVal == null) retVal = new Variable(VarType.INT, -1);
			System.out.println("Program exited, return value : " + retVal.getInteger());
		} catch (Exception e) {
			System.out.println("RUNTIME ERROR");
			e.printStackTrace();
		}
	}
}
